const ncname = `[a-zA-z_][\\-\\.0-9_a-zA-z]*`;// 用来描述标签的
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`);// 标签开头的正则 捕获的内容是开始标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);// 标签结尾的正则 捕获的内容是结束标签名
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^'`]*)'+|([^\s"'=<>]+)))?/; // 匹配属性的 分组1拿到的是属性名 分组3，4，5拿到的是key对应的值
const startTagClose = /^\s*(\/?)>/;// 匹配标签结束的 />  >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // 匹配{{xxx}}中间的内容

// 解析HTML获取ast语法树
function parseHTML(html) {
    // 树的操作，需要根据开始标签和结束标签产生一棵树
    let root;
    // 如何构建父子关系，利用栈存储
    let stack = [];
    // 解析一点删一点，直至html为空
    while (html) {
        // 解析开始结束标签
        let textEnd = html.indexOf('<');
        if (textEnd === 0) {// 标签
            // 开始标签
            const startTagMatch = parseStartTag(); // {tag:'div', attrs:[{name:'id',value:'app'}]}
            if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs);
                continue;
            }
            // 结束标签
            let endTagMatch;
            if ((endTagMatch = html.match(endTag))) {
                end(endTagMatch[1]);
                advance(endTagMatch[0].length);
                continue;
            }
        }
        // 解析文本内容
        let text;
        if (textEnd >= 0) {
            text = html.substring(0,textEnd);
        }
        if (text) {
            advance(text.length);
            chars(text);
        }
    }
    /*
    * 构建语法树
    * {
    *   tag: 'div',
    *   attrs:[{name:'id',value:'app'},{xxx:xxx}],
    *   children:[
    *       {
    *           tag: 'h1',
    *           attrs:[]
    *           children:[]
    *       },
    *   ],
    *   type: 1,
    * }
    * */
    // 构建ast元素
    function createASTElement(tagName, attrs) {
        return {
            tag: tagName,
            attrs: attrs,
            children: [],
            parent: null,
            type: 1
        }
    }
    function start(tagName, attrs) {
        let element = createASTElement(tagName, attrs);
        // 根为空将第一个元素设置为根
        if (!root) {
            root = element;
        }
        // 设置父子关系
        let parent = stack[stack.length-1];// 栈中的最后一个作为当前元素的父元素
        if (parent) {
            parent.children.push(element);// 父元素记录子元素
            element.parent = parent;// 子元素记录父元素
        }
        // 将元素放入栈中
        stack.push(element);
    }
    function end(tagName) {
        // 取到结束标签时将该元素从栈中删除
        stack.pop();
    }
    function chars(text) {
        // 替换空格
        text = text.replace(/\s/g,'');
        // 若有文本记录到父元素中
        if (text) {
            // 取栈中的最后一个元素
            let parent = stack[stack.length-1];
            // type：3就是文本类型
            parent.children.push({
                type: 3,
                text
            });
        }
    }
    // 每次根据传来的长度截取html
    function advance(n) {
        html = html.substring(n);
    }
    // 解析开始标签
    function parseStartTag() {
        const matches = html.match(startTagOpen);
        if (!matches) {// 匹配不到则不是开始标签
            return;
        }
        const match = {
            tagName: matches[1],
            attrs: []
        };
        // 删除开始标签
        advance(matches[0].length);
        let end, attr;
        // 循环至结束标签
        while (!(end = html.match(startTagClose))) {
            attr = html.match(attribute);
            // 将标签属性添加进去属性数组
            match.attrs.push({name: attr[1], value: attr[3]||attr[4]||attr[5]||true});
            // 删除添加过的属性
            advance(attr[0].length);
        }
        // 将标签结束的尖括号删除
        if (end) {
            advance(end[0].length);
            return match;
        }
    }
    return root;
}

// 生成属性字符串
function genProps(attrs) {
    let str = '';
    for (let i=0,length=attrs.length; i<length; i++) {
        let attr = attrs[i];
        // 特殊属性style = "{color:red, background:blue}"
        if (attr.name === 'style') {
            let obj = {};
            attr.value.split(';').reduce((memo,current) => {
                let [key,value] = current.split(':');
                memo[key] = value;
                return memo;
            },obj);
            attr.value = obj;
        }
        str += `${attr.name}:${JSON.stringify(attr.value)},`;
    }
    return `{${str.slice(0,-1)}}`;
}

// 子元素生成
function gen(node) {
    if (node.type === 1) { // 类型为元素, 重新走一遍genCode
        return genCode(node);
    } else if (node.type===3) { // 类型为文本
        let text = node.text;
        if (!defaultTagRE.test(text)) {
            // 不带表达式的 纯文本 转换成字符串返回
            return `_v(${JSON.stringify(text)})`;
        } else {
            // 带表达式的 {{xxx}}
            let tokens = [];
            let match;
            // exec 遇到全局匹配会有 lastIndex问题 每次匹配前需要将lastIndex置为0
            let startIndex = defaultTagRE.lastIndex = 0;
            // 循环一直匹配至{{xxx}}空为止
            while (match = defaultTagRE.exec(text)) {
                // ab {{xxx}} {{yyy}} cd
                let endIndex = match.index;// 匹配到的索引
                // 结束索引大于开始的索引 则为文本 直接将文本添加到里面
                // 先放ab
                if (endIndex > startIndex) { // 说明有ab
                    tokens.push(JSON.stringify(text.slice(startIndex,endIndex)));
                }
                // 放 xxx yyy 转换成_s(xxx)
                tokens.push(`_s(${match[1].trim()})`);
                // 加偏移量
                startIndex = endIndex + match[0].length;
            }
            // 放 cd
            if (startIndex < text.length) {
                tokens.push(JSON.stringify(text.slice(startIndex)));
            }
            // 组合成最终语法并返回 _v('ab'+_s(xxx)+_s(yyy)+'cd')
            return `_v(${tokens.join('+')})`;
        }
    }
}

// 生成子元素字符串
function genChildren(ast) {
    const children = ast.children;
    return children.map(child => gen(child)).join(',');
}

// 将ast语法树转换成函数字符串
function genCode(ast) {
    let code;
    // _c()元素 _v()文本 _s()变量
    // _c('div',{className:'xxx'},_v('hello world'))
    code = `_c('${ast.tag}',${
        ast.attrs.length ? genProps(ast.attrs) : 'undefined'
    }${
        ast.children ? ',' + genChildren(ast) : ''
    })`;
    return code;
}

// 将模板变成render函数 通过 with + new Function的方式让字符串变成js语法来执行
export function compileToFunction(template) {
    // 获取ast语法树
    let ast = parseHTML(template);
    // 通过ast语法树转换成render函数
    let code = genCode(ast);

    // 生成render函数 将字符串变成函数
    const render = new Function(`with(this){return ${code}}`);

    return render;
}






// 将template转换成ast语法树 -> 再将语法树转换成一个字符串拼接在一起

// ast 使用来描述语言本身的，语法中没有的不会描述出来，不能自己增添属性
/*let obj = {
    tag,
    attrs,
    context,
    component
}*/
// vdom 虚拟dom 描述真实dom元素的，可以自己增添属性
/*
let obj = {name:'lg',age:21};
function render() {
    with(this) {
        console.log(name);
        console.log(age);
    }
}
render.call(obj);
*/

/*(function anonymous(
) {
    with(this){
        return
        _c('div',
            {id:"app",style:{"width":" 100px","height":" 100px"},disabled:true},
            _v(_s(msg)),
            _c('h1',undefined,_v(_s(123)+",world.")),
            _c('span',undefined,_v("123"),_c('i',undefined,_v("456")))
        )
    }
})*/
